package com.jerry.seckill.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import com.jerry.seckill.dao.SeckillDao;
import com.jerry.seckill.dao.SuccessKilledDao;
import com.jerry.seckill.dao.cache.RedisDao;
import com.jerry.seckill.dto.ExecutionResult;
import com.jerry.seckill.dto.Exposer;
import com.jerry.seckill.entity.Seckill;
import com.jerry.seckill.entity.SuccessSeckilled;
import com.jerry.seckill.enums.SeckillStateEnum;
import com.jerry.seckill.exception.RepeatSeckillException;
import com.jerry.seckill.exception.SeckillCloseException;
import com.jerry.seckill.exception.SeckillException;
import com.jerry.seckill.service.SeckillService;

/** 
  * @describe 接口实现 
  * @Component 代表容器中一个组件的实例
  * @Service 代表一个service
  * @Dao 代表一个dao
  * @Controller 代表一个controller
  */ 
@Service 
public class SeckillServiceImpl implements SeckillService{ 
	private Logger logger = LoggerFactory.getLogger(this.getClass()); 
	
	//注入service依赖
	@Autowired 
	private SeckillDao seckillDao; 
	@Autowired 
	private SuccessKilledDao successKilledDao; 
	@Autowired 
	private RedisDao redisDao; 
	
	// md5盐值字符串，用于混淆md5 
	private final String slat = "fdhasjfhu5GERGTEiweayrwe$%#$%$#546@wdasdfas"; 
	
	/** 
	  * 查询所有秒杀记录 
	  * @return 
	  */ 
	public List<Seckill> getSeckillList() { 
		return seckillDao.queryAll(0,4); 
	} 
	
	/** 
	  * 查询单个秒杀记录 
	  * @param seckillId 
	  * @return 
	  */ 
	public Seckill getById(long seckillId) { 
		return seckillDao.queryById(seckillId); 
	} 
	
	/** 
	  * 秒杀开启时输出秒杀接口地址，否则输出系统时间和秒杀时间 
	  * @param seckillId 
	  */ 
	public Exposer exportSeckillUrl(long seckillId) { 
		// 优化点：缓存优化：超时的基础上维护一致性 
		//1：访问redis 
		Seckill seckill = redisDao.getSeckill(seckillId); 
		if(null == seckill){ 
			//2：访问数据库 
			seckill = seckillDao.queryById(seckillId); 
			if(null == seckill){ 
				return new Exposer(false,seckillId); 
			} else { 
				//3：放入redis 
				redisDao.putSeckill(seckill); 
			} 
		} 
		
		Date startTime = seckill.getStartTime(); 
		Date endTime = seckill.getEndTime(); 
		// 系统时间 
		Date nowTime = new Date(); 
		//秒杀还未开始或已结束（现在时间 < 开始时间，现在时间 > 结束时间）
		if(nowTime.getTime() < startTime.getTime() 
				|| nowTime.getTime() > endTime.getTime()){ 
			return new Exposer(false,seckillId,nowTime.getTime(),startTime.getTime(),endTime.getTime()); 
		} 
		
		// 转换特定字符串的过程，不可逆 
		String md5 = this.getMD5(seckillId); 
		
		return new Exposer(true,md5,seckillId); 
	} 
	
	/** 
	  * 执行秒杀操作  
	  * @param seckillId 
	  * @param userPhone 
	  * @param md5 
	  */ 
	/** 
	  * 使用注解控制事务方法的优点： 
	  * 1：开发团队达成一致约定，明确标注事务方法的编程风格 
	  * 2：保证事务方法的执行时间尽可能短，不要穿插其他网络操作，RPC/HTTP请求或者剥离到事务方法外部 
	  * 3：不是所有的方法都需要事务，如只有一条修改操作，只读操作不需要事务控制 
	  */ 
	@Transactional 
	public ExecutionResult executeSeckill(long seckillId, String userPhone, String md5) 
			throws SeckillException, RepeatSeckillException, SeckillCloseException { 
		if(null == md5 || !md5.equals(getMD5(seckillId))){ 
			throw new SeckillException("seckill data rewrite"); 
		} 
		// 执行秒杀逻辑：减库存 + 记录购买行为 
		Date nowTime = new Date(); 
		try{ 
			// 记录购买行为 
			int insertCount = successKilledDao.insertSuccessKilled(seckillId,userPhone); 
			// 唯一性：seckillId,userPhone 
			if (insertCount <= 0){ 
				// 重复秒杀 
				throw new RepeatSeckillException("seckill repeated"); 
			}else{ 
				// 减库存，热点商品竞争 
				int updateCount = seckillDao.reduceNumber(seckillId,nowTime); 
				if(updateCount <= 0){ 
					// 没有更新到记录，秒杀结束   rollback
					throw new SeckillCloseException("seckill is closed"); 
				}else{ 
					// 秒杀成功    commit
					SuccessSeckilled successSeckilled = successKilledDao.queryByIdWithSeckill(seckillId,userPhone); 
					return new ExecutionResult(seckillId, SeckillStateEnum.SUCCESS,successSeckilled); 
				} 
			} 
		} catch (SeckillCloseException e1){ 
			throw e1; 
		} catch (RepeatSeckillException e2){ 
			throw e2; 
		}catch (Exception e){ 
			logger.error(e.getMessage(),e); 
			// 把所有编译期异常转化成运行期异常 
			throw new SeckillException("seckill inner error:"+e.getMessage()); 
		} 
	} 
	
	/** 
	  * 执行秒杀操作，存储过程 
	  * @param seckillId 
	  * @param userPhone 
	  * @param md5 
	  */ 
	public ExecutionResult executeSeckillProcedure(long seckillId, String userPhone, String md5){ 
		if(md5 == null || !md5.equals(getMD5(seckillId))){ 
			return new ExecutionResult(seckillId,SeckillStateEnum.DATA_REWRITE); 
		} 
		Date killTime = new Date(); 
		Map<String,Object> map = new HashMap<String, Object>(); 
		map.put("seckillId",seckillId); 
		map.put("phone",userPhone); 
		map.put("killTime",killTime); 
		map.put("result",null); 
		
		// 执行存储过程，result被赋值 
		try { 
			seckillDao.killByProcedure(map); 
			// 获取result 
			int result = MapUtils.getInteger(map,"result",-2); 
			if(result == 1){ 
				SuccessSeckilled sk = successKilledDao.queryByIdWithSeckill(seckillId,userPhone); 
				return new ExecutionResult(seckillId,SeckillStateEnum.SUCCESS,sk); 
			} else { 
				return new ExecutionResult(seckillId,SeckillStateEnum.stateOf(result)); 
			} 
		}catch (Exception e){ 
			logger.error(e.getMessage(),e); 
			return new ExecutionResult(seckillId,SeckillStateEnum.INNER_ERROR); 
		} 
	} 
	
	private String getMD5(long seckillId){ 
		String base = seckillId + "/" + slat; 
		String md5 = DigestUtils.md5DigestAsHex(base.getBytes()); 
		return md5; 
	} 
	
}

